/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.peer.dao.statistics;

import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.ourgrid.common.interfaces.to.GridProcessAccounting;
import org.ourgrid.common.interfaces.to.GridProcessState;
import org.ourgrid.common.interfaces.to.ProcessCommand;
import org.ourgrid.common.interfaces.to.RequestSpec;
import org.ourgrid.common.spec.job.JobSpec;
import org.ourgrid.common.spec.job.TaskSpec;
import org.ourgrid.common.spec.worker.WorkerSpec;
import org.ourgrid.common.statistics.beans.peer.Attribute;
import org.ourgrid.common.statistics.beans.peer.Command;
import org.ourgrid.common.statistics.beans.peer.GridProcess;
import org.ourgrid.common.statistics.beans.peer.Job;
import org.ourgrid.common.statistics.beans.peer.Login;
import org.ourgrid.common.statistics.beans.peer.Peer;
import org.ourgrid.common.statistics.beans.peer.Task;
import org.ourgrid.common.statistics.beans.peer.User;
import org.ourgrid.common.statistics.beans.peer.Worker;
import org.ourgrid.common.statistics.beans.status.ExecutionStatus;
import org.ourgrid.common.statistics.util.hibernate.HibernateUtil;
import org.ourgrid.peer.dao.UsersDAO;
import org.ourgrid.peer.to.Request;

import br.edu.ufcg.lsd.commune.Application;
import br.edu.ufcg.lsd.commune.CommuneRuntimeException;
import br.edu.ufcg.lsd.commune.container.logging.CommuneLogger;
import br.edu.ufcg.lsd.commune.container.logging.CommuneLoggerFactory;
import br.edu.ufcg.lsd.commune.identification.DeploymentID;
import br.edu.ufcg.lsd.commune.identification.ServiceID;
import br.edu.ufcg.lsd.commune.processor.filetransfer.TransferProgress;

/**
 * 
 */
public class JobDAO extends EntityDAO {
	private CommuneLogger logger;
	
	public JobDAO(Application application) {
		super(application);
		 logger = CommuneLoggerFactory.getInstance().gimmeALogger(getClass());
	}

	public void insert(Job job) {
		logger.enter();
		Session session = HibernateUtil.getSession();
		session.save(job);
		session.flush();
		logger.leave();
	}
	
	public void insert(GridProcess process) {
		logger.enter();
		Session session = HibernateUtil.getSession();
		session.save(process);
		session.flush();
		logger.leave();
	}

	public void insert(Task task) {
		logger.enter();
		Session session = HibernateUtil.getSession();
		session.save(task);
		session.flush();
		logger.leave();
	}

	public Task findByRequestAndSequence(long requestId, int taskSequenceNumber) {
		logger.enter();
		
		Criteria criteria = HibernateUtil.getSession().createCriteria(Task.class);
		criteria.createCriteria("job").add(Restrictions.eq("requestId", requestId));
		criteria.add(Restrictions.eq("sequenceNumber", taskSequenceNumber));
		
		Task task = (Task) criteria.uniqueResult();
		
		logger.leave();
		return task;
	}

	public void insert(Command command) {
		logger.enter();
		Session session = HibernateUtil.getSession();
		session.save(command);
		session.flush();
		
		logger.leave();
	}

	public Job findByRequestId(long requestId) {
		logger.enter();
		
		Criteria criteria = HibernateUtil.getSession().createCriteria(Job.class);
		criteria.add(Restrictions.eq("requestId", requestId));
		Job result = (Job) criteria.uniqueResult();
		
		logger.leave();
		return result;
	}

	public void update(Job job) {
		logger.enter();
		Session session = HibernateUtil.getSession();
		session.update(job);
		session.flush();

		logger.leave();
	}
	
	public void addRequest(Request request) {
		
		ServiceID userServiceID = request.getConsumer().getDeploymentID().getServiceID();
		String userLogin = getAddress(userServiceID);
		
		UsersDAO userDAO = getDAO(UsersDAO.class);
		User userObj = userDAO .findByAddress(userLogin);
		if (userObj == null) {
			throw new CommuneRuntimeException("The user is not added: " + userLogin);
		}

		LoginDAO loginDAO = getDAO(LoginDAO.class);
		Login login = loginDAO .findCurrentLogin(userObj);
		
		if (login == null) {
			throw new CommuneRuntimeException("The user is not logged: " + userLogin);
		}
		
		RequestSpec requestSpec = request.getSpec();
		JobSpec jobSpec = requestSpec.getJobSpec();

		Job job = new Job();
		job.setCreationTime(now());
		job.setJobId(requestSpec.getJobId());
		job.setRequestId(requestSpec.getRequestId());
		job.setLabel(jobSpec.getLabel());
		job.setLastModified(now());
		job.setLogin(login);
		//login.getJobs().add(job);
		job.setMaxFails(requestSpec.getMaxFails());
		job.setMaxReplicas(requestSpec.getMaxReplicas());
		job.setRequirements(requestSpec.getRequirements());
		job.setStatus(ExecutionStatus.UNSTARTED);
		
		insert(job);
		
		List<TaskSpec> taskSpecs = jobSpec.getTaskSpecs();
		
		int i = 1;
		for (TaskSpec taskSpec : taskSpecs) {
			
			Task task = new Task();
			task.setSequenceNumber(i++);				
			task.setActualFails(0);
			task.setJob(job);
			//job.getTasks().add(task);
			task.setLastModified(now());
			task.setRemoteExec(taskSpec.getRemoteExec());
			task.setSabotageCheck(taskSpec.getSabotageCheckCommand());
			task.setStatus(ExecutionStatus.UNSTARTED);

			insert(task);
		}
		
	}
	
	public void addProcessAccounting(GridProcessAccounting replicaAccounting) {

		long requestId = replicaAccounting.getRequestSpec().getRequestId();
		int taskSequenceNumber = replicaAccounting.getTaskSequenceNumber();
		
		WorkerDAO workerDAO = getDAO(WorkerDAO.class);
		
		Task task = findByRequestAndSequence(requestId, taskSequenceNumber);
		if (task == null) {
			throw new CommuneRuntimeException("Task not found. Request id: " + requestId + ", Task number: " +
					taskSequenceNumber);
		}
		
		DeploymentID workerDeploymentID = new DeploymentID(replicaAccounting.getWorkerID());
		ServiceID workerServiceID = workerDeploymentID.getServiceID();
		
		Worker worker = workerDAO.findActiveWorker(getAddress(workerServiceID));
		
//		if (worker == null) {
//			
//			//Local Worker
//			if (myDeploymentID.getContainerID().equals(wpID.getContainerID())) {
//				throw new CommuneRuntimeException("Unknown local worker: " + workerServiceID);
//			
//			} else {
//				worker = addRemoteWorker(workerServiceID, 
//						replicaAccounting.getWorkerSpec(), wpID);
//			}
//		}
		
		if (task.getJob().getStatus().equals(ExecutionStatus.UNSTARTED)) {
			task.getJob().setStatus(ExecutionStatus.RUNNING);
		}

		GridProcess process = new GridProcess();
		process.setCreationTime(replicaAccounting.getCreationTime());

		process.setExitValue(replicaAccounting.getExitValue());
		if (process.getExitValue() != null && process.getExitValue() != 0) {
			process.setErrorCause(replicaAccounting.getErrorCause());
			process.setExecutionErrorType(replicaAccounting.getExecutionErrorType());
		}

		process.setInitBeginning(replicaAccounting.getInitBeginning());
		process.setInitEnd(replicaAccounting.getInitEnd());
		process.setRemoteBeginning(replicaAccounting.getRemoteBeginning());
		process.setRemoteEnd(replicaAccounting.getRemoteEnd());
		process.setFinalBeginning(replicaAccounting.getFinalBeginning());
		process.setFinalEnd(replicaAccounting.getFinalEnd());
		
		process.setLastModified(now());
		process.setLatestPhase(replicaAccounting.getLatestPhase());
		process.setSabotageCheck(replicaAccounting.getSabotageCheck());
		
		GridProcessState state = replicaAccounting.getState();
		if (GridProcessState.ABORTED.equals(state)) {
			process.setStatus(ExecutionStatus.ABORTED);
		} else if (GridProcessState.CANCELLED.equals(state)) {
			process.setStatus(ExecutionStatus.CANCELLED);
		} else if (GridProcessState.FAILED.equals(state)) {
			process.setStatus(ExecutionStatus.FAILED);
		} else if (GridProcessState.FINISHED.equals(state)) {
			process.setStatus(ExecutionStatus.FINISHED);
		} else if (GridProcessState.RUNNING.equals(state)) {
			process.setStatus(ExecutionStatus.RUNNING);
		} else if (GridProcessState.SABOTAGED.equals(state)) {
			process.setStatus(ExecutionStatus.SABOTAGED);
		} else if (GridProcessState.UNSTARTED.equals(state)) {
			process.setStatus(ExecutionStatus.UNSTARTED);
		}
		
		process.setStderr(replicaAccounting.getStderr());
		process.setStdout(replicaAccounting.getStdout());
		process.setTask(task);
		//task.getProcesses().add(process);
		
		process.setWorker(worker);
		
		insert(process);
		
		
		List<ProcessCommand> initCommands = replicaAccounting.getInitCommands();
		
		if (initCommands != null) {
			
			for (ProcessCommand pCommand : initCommands) {
				insertCommand(replicaAccounting, pCommand, process);
			}
		}
		
		
		List<ProcessCommand> finalCommands = replicaAccounting.getFinalCommands();
		
		if (finalCommands != null) {
			
			for (ProcessCommand pCommand : finalCommands) {
				insertCommand(replicaAccounting, pCommand, process);
			}
		}
	}
	
	private void insertCommand(GridProcessAccounting replicaAccounting, ProcessCommand pCommand, GridProcess process) {
		
		TransferProgress progress = replicaAccounting.getTransferProgress(pCommand.getHandle());
		
		if (pCommand != null) {
			Command command = new Command();
			command.setDestination(pCommand.getDestination());
			
			command.setFileName(pCommand.getFileName());
			
			if (progress != null) {
				command.setFileSize(progress.getAmountWritten());
				command.setProgress(progress.getProgress());
				command.setStatus(progress.getNewStatus().toString());
				double tRate = progress.getAmountWritten() / (pCommand.getTransferEnd() - pCommand.getTransferBegin());
				command.setTransferRate(tRate);
			}	
			command.setTransferBegin(pCommand.getTransferBegin());
			command.setTransferEnd(pCommand.getTransferEnd());
			command.setProcess(process);
			command.setLastModified(now());
			command.setName(pCommand.getName());
			command.setSource(pCommand.getSource());
			
			insert(command);
		}	
	}
	
	public void finishRequest(Request request, boolean clientFailure) {
		
		
		Job job = findByRequestId(request.getSpec().getRequestId());
		
		job.setLastModified(now());
		
		if (clientFailure) {
			job.setStatus(ExecutionStatus.FAILED);
		
		} else {
			job.setStatus(ExecutionStatus.FINISHED);
			job.setFinishTime(now());
		}
		
		update(job);
		
	}

}
